묻고 답해요
129만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결피그마(Figma)를 활용한 UI디자인 입문부터 실전까지 A to Z
스타일 및 텍스트박스 관련 문의드립니다.
안녕하세요 에릭님! 수업을 듣다 질문이 있어 문의남깁니다.오랫만에 다시 들으며 질문이 생겨서 남기는것인데, 혹시 다른 강의에서 설명해주셨던 부분이면 죄송합니다^^;우선 첫번째로는, 아래 첨부한 이미지와 같이 텍스트를 입력하고 여백을 잴 때 텍스트박스 기준으로 사이즈를 재게 되는데, out stroke처리를 하는것과 텍스트박스 크기를 조정하는것에서 여백 차이가 발생합니다.이럴때 처음에는 텍스트 박스로 작업을 하더라도, 최종 개발자에게 넘겨주기 전에는 스트로크 처리를 한 뒤에 여백이나 정렬을 다시 정리해야 하는걸까요?두번째 질문은, 스타일 가이드를 잡을 때 보통 몇개정도의 스타일 가이드를 잡고 진행하는것이 좋을까요?예를들어서 같은 26사이즈에, bold와 26사이즈 light가 있으면 각각 스타일을 등록해놔야 하는것인지그리고 버튼에 들어가는 텍스트 스타일은 Heading이나 Body외에 Button으로 별도 스타일 네이밍을 해야하는것인지 궁금합니다. 같은 사이즈나 굵기여도, 글씨체가 달라진다면 그것도 별도 스타일로 지정해야하나요? 마지막으로 개발자에게 작업 시안을 넘길 때 현업에서 필수로 넘기는 정보가 있을까요? 예를들어 padding, margin값을 알 수 있는지 문의가 들어왔었는데, 이런것 외에도 색상코드나 텍스트 사이즈같은걸 사전에 스타일로 잡으면서 값을 다 정리해놓고 개발자분에게 전달해야하는것인지..? 궁금합니다. ^^처음에 강의를 들을땐 다 알것만 같았는데, 다시 실제 실습이나 작업을 해보며 강의를 재수강하니 까먹었던것이나 당시에 100%이해하지 못하고 넘어갔던것을 다시 학습하게 되어 좋기도 하고, 귀찮게 질문드려 죄송한 마음도 드네요.좋은 강의와 답변에 늘 감사한 마음드립니다. 감사합니다!!
-
해결됨뉴욕 프로덕트 디자이너가 알려주는, 입문자를 위한 UX디자인 개론
좋은 UX와 안좋은 UX 사례
안녕하세요! 제가 자주 사용하는 컬리와, 오늘의집 두가지 앱으로 과제 제출합니다. 이전 검색 결과에 따라 상품추천 해주던 것이 생각나 선택하게되었어요. 식품, 인테리어 각 다른 분야의 서비스지만, 두 앱 모두 맞춤형 콘텐츠를 추천하는 개인화 서비스를 제공하고 있었는데요. 이외에도 두 앱을 비교하며 여러 공통점으로 찾아 볼 수 있었습니다. 배웠던 UX입문 원리들에 따라 분류해보았습니다.1.낮은의도와 높은 의도를 가진 사용자를 고려한 콘텐츠 구성 공통적으로 검색데이터를 활용한 추천상품(이 상품 어때요?/최근관심있게본#00)과 사용자의도를 고려한 메뉴구성이 눈에 띄었는데요. 추천상품(컬리추천/취향의발견), 혜택상품(알뜰쇼핑, 특가혜택/오늘의 딜)이 공통적인 특징이라 생각했습니다.또 하나 공통적으로 컬리는 ‘컬리로그’의 요리•뷰티와 관련한 일상사진으로 오늘의 집은 ‘커뮤니티’의 집들이 소개 콘텐츠로 상품을 간접노출 하고 있습니다. 다만 오늘의 집 경우 이벤트 영역 아래 사용자가 자주 찾을 법한 메뉴를 아이콘으로 구성해 보여주어 한눈에 필요한 메뉴를 찾을 수 있다고 생각했습니다. 다음은 안좋은 UX사례로1.모바일앱과 PC웹의 다른 메뉴명 - 오늘의 집 어떤 이유가 있는 걸까요? 오늘의 집의 경우 PC웹에서 확인해보니 하위 콘텐츠는 동일한데 메뉴명이 다른 것을 발견했습니다. PC웹에서는 ‘커뮤니티’, ‘스토어’, ‘이사/시공/수리’로 최상위 메뉴구조를 앱의 경우는 커뮤니티를 ‘둘러보기’, 스토어를 ‘쇼핑’으로 보여주고 있었습니다. (사례를 찾을 당시는 스토어 였는데 글을 올리는 시점엔 쇼핑으로 변경되었네요 ?)앱 최상단에 검색바의 경우도 앱은 오늘의 집 통합검색, 쇼핑검색이고 PC는 통합검색과 스토어검색입니다. 이부분이 수업에서 배운 일관성의 원리에 어긋나는 것은 아닌지 해서 UX의 안좋은 사례로 선정하였습니다. 2.컬리의 마켓 or 뷰티 탭 전환 시 어딘가 불편한 UX 마켓컬리와 뷰티컬리의 탭전환은 네비게이션 바 가운데 토글 스위치 형태의 UI로 이루어집니다. 여기서 마켓과 뷰티의 전환 시 하단탭바의 가운데 아이콘 ‘메뉴’를 누르면 하위 메뉴들도 바뀌어있는 것을 알 수 있습니다. 두번째 메뉴인 컬리로그도 탭 전환에 따라 전체 페이지 내 요리와 뷰티 중 해당하는 콘텐츠가 상위에 노출 되는 것 같습니다. (여러번 마켓과 뷰티를 전환해봄) 최상위 메인기능 전환이 이루어지는 하단탭바에 컬리의 탭 (마켓or뷰티)을 붙이다보니 UX적으로 아쉽다고 생각했습니다. 여기서 탭바의 메뉴구조를 조금 손보면 사용자의 혼선을 줄일 순 있지않을까.. 생각해보기도 했습니다. (홈을 가운데 정렬하고 좌우로 기타 메뉴를 배치한 후 컬리탭을 홈으로 전환한다면..) 현재 앱에서 상단과 우측 하단 두 곳에 전환 탭을 중복 제공하고 있는데 어쩌면 엄지영역을 고려해 한 화면에 두곳이나 중복으로 배치한 것이 아닌지 추측해보았습니다.읽어주셔서 감사합니다.
-
미해결뉴욕 프로덕트 디자이너가 알려주는, 입문자를 위한 UX디자인 개론
UI 디자인 원리 적용된 좋은 사례, 나쁜 사례
좋은 사례뱅크샐러드뱅크 샐러드 어플을 최근 들어 다시 쓰고 있습니다. 기존 은행 어플과 달리 정말 직관적인 UI를 많이 느끼고 있는데요.그 중에서 휴리스틱 평가 상태 안내가 적용된 걸로 생각되는 예입니다. 동의 사항이 나올때 아래 버튼이 '아래로 스크롤' 통해서 제가 더 읽어야 하는 것이 있다는 것을 말해주고 끝까지 읽으면 그 버튼 내용이 바뀌면서 '내 데이터 조회하기'로 바뀌어 다 읽고 그 다음으로 진행됨을 나타내는 CTA가 반영되어 이해하기가 수월했습니다나쁜 사례KB Star Banking많은 정보의 양을 너무 작은 텍스트, 그리고 그 글이 스크롤과 더불어서 좌,우로 읽어야 할 것도 있습니다. 뭔가 읽고 싶어도 읽고 쉽지 않게 만듭니다. 진행사항도 없고 제가 하는 행동이 하나도 UI요소로 반영이 안되어 더이상 여기선 '전체 동의' 버튼 누르고 빠르게 나가고만 싶었습니다. 뭔가 사용자로 하여금 읽게끔 배려해주는 것이 전혀 없다는 것을 느꼈습니다
-
미해결부트스트랩 5(Bootstrap 5) - 기초부터 웹 프로젝트 만들기
a:hover {color }
질문글을 작성하다가 원인을 찾아서 문제해결했습니다만, 추후 다른 분께 도움이 될까 봐 간략하게 올립니다.문제#intro .intro-first-card .overlay a:hover { color: #fafa6f; }위 코드가 동작하지 않았습니다.원인bootstrap.min.css 버전에 따른 .link-light 차이강의&수업자료 버전은bootstrap@5.0.2/dist/css/bootstrap.min.css을 사용 .link-light {color: #f8f9fa;}제 버전은 bootstrap@5.2.1/dist/css/bootstrap.min.css .link-light {color: #f8f9fa!important;}해결#intro .intro-first-card .overlay a:hover { color: #fafa6f !important; }
-
미해결피그마(Figma)를 활용한 UI디자인 입문부터 실전까지 A to Z
짝수/홀수 프레임 width와 object 크기 결정 문의
에릭님 안녕하세요~강의를 듣던 중에 궁금한 점이 있습니다.저희가 프로젝트에 따라 다르겠지만 보통 프레임을 선택할 때 안드로이드는 360*XXX iPhone은 375*812 사이즈로 선택하고 작업을 진행한다고 알고 있습니다.관련해서 제가 프레임 중앙에 어떤 객체 (예. 정사각형 카드)를 배치한다고 했을 때 안드로이드에서는 짝수 (240*240)의 경우 가운데 정렬을 했을 때 좌/우 60씩 남지만 아이폰에서는 67/68로 배치가 됩니다. (피그마에서 억지로 가운데 정렬을 할 수 있을 것 같긴 한데 그러면 그게 0.5단위여서 개발에 전달할 때 문제가 있지 않을까 싶기도 하고 다른 객체들을 디자인 할 때 방해가 되지 않을까 싶습니다.)위와 같은 예시로 한 앱을 두 OS에서 동일하게 보여주고 싶다면 객체 크기를 어떻게 설정하는게 좋을까요?아이폰에서도 프레임의 짝수 홀수의 width가 섞여 있는데 정사각형/원형이 개체를 정 가운데 배치할 때 어떻게 기준을 잡아야 할까요?더불어 안드에서는 8의 배수로 디자인 하는 것을 많이 권장하시는데 아이폰에서도 그런 기준이 있을까요? 미리 감사합니다~
-
미해결디자이너의 스킬업을 위한 Lottie 완벽 이해하기
아이콘 클릭시 애니메이션 질문 드립니다.
안녕하세요강좌 잘 보고 있습니다.현 강좌에서는 아이콘 클릭시 애니메이션이 실행이 되는데아이콘 클릭시 클릭한 아이콘만 애니메이션 되고다른 아이콘들은 애니메이션이 안된 비활성화 상태로 하고 싶은데(맨 처음에 보여주신 에프터이펙트에서 제작한 예제처럼)script 처리를 어떻게 해주면 될까요미리 감사드립니다.
-
해결됨애플 웹사이트 인터랙션 클론!
messageA_opacity_out 글자가 사라지지 않는 문제
강사님 안녕하세요. 특정 타이밍 스크롤 애니메이션 적용하기 2번째 시간 transform 적용하기 전까지 들었습니다. (13분쯤 ) opacity: 1;로 글자가 점점 나타나는 부분까지는 했는데 글자가 사라지지 않아 console 로 messageA_opacity_out을 찍어봤습니다. console 창에서는 messageA_opacity_out 숫자가 제대로 줄어드는데 글자는 사라지지가 않습니다. 앞뒤로 강의를 돌려서 반복해서 다시 들었으나 문제점을 찾지 못하여 질문을 드립니다. 답변해주시면 감사합니다! https://florentine-trombone-82f.notion.site/0398c6579ce64b948fae207605e623ad 따로 파일을 올릴 수 없어 페이지에 올려둡니다
-
미해결애플 웹사이트 인터랙션 클론!
overflow-x:hidden을 사용했는데, 모바일 화면에서 우측이 잘리고 가로 스크롤이
d위 이미지처럼 가로 스크롤이 생기는 데 overflow-x: hidden 기능이 적용되지 않아서 생기는 문제일까요?
-
미해결애플 웹사이트 인터랙션 클론!
스크롤이 적용이 안되는것같아요!ㅜㅜ
스크롤도 늘어나질 않고 스티키도 안보이고 씬도 변하질 않고 계속 0에 있어요ㅜㅜ 첨부하겠습니당 const main = () => { let y0ffset = 0; const sceneInfo = [ { //0 type: 'sticky', heightNum: 5, //브라우저 높이의 5배로 scrollHeight 세팅 scrollHeight: 0, objs: { container: document.querySelector('#scroll-section-0') } }, { //1 type: 'normal', heightNum: 5, scrollHeight: 0, objs: { container: document.querySelector('#scroll-section-1') } }, { //2 type: 'sticky', heightNum: 5, scrollHeight: 0, objs: { container: document.querySelector('#scroll-section-2') } }, { //3 type: 'sticky', heightNum: 5, scrollHeight: 0, objs: { container: document.querySelector('#scroll-section-3') }, } ]; function setLayout() { //각 스크롤 섹션의 높이 세팅 for (let i = 0; i < sceneInfo.length; i++) { sceneInfo[i].scrollHeight = sceneInfo[i].heightNum * window.innerHeight; sceneInfo[i].objs.container.style.height = `${sceneInfo[i].scrollHeight}px`; } console.log(sceneInfo); } function scrollLoop() { } window.addEventListener('resize',setLayout); window.addEventListener('scroll',() => { y0ffset = window.pageXOffset; scrollLoop(); }); setLayout(); }; main();
-
해결됨[2024년 출제기준] 웹디자인기능사 실기시험 완벽 가이드(HTML+CSS+JQUERY)
공지사항 갤러리 탭메뉴 div a태그
공지사항 갤러리 탭메뉴랑 공지사항 갤러리 별도 구성메뉴가 있는데 div 안에 '공지사항', '갤러리' 제목넣는 곳에 태그를 span은 별도메뉴에 쓰고, 탭메뉴에는 a태그로 쓰면 되나요? 상호작용이 들어가는 부분은 tab을 눌렀을 때 확인되어야 해서 a 임시링크를 걸어주는 걸로 기억하는데, 별도 구성인 경우엔 a태그 말고 span으로 줘도 무관한가요?
-
해결됨[2024년 출제기준] 웹디자인기능사 실기시험 완벽 가이드(HTML+CSS+JQUERY)
라이브 서버가 네이버웨일에서는 안되는건가용?
안녕하세요 비주얼스튜디오 세팅부분 들으면서 선생님이 하시는거 따라 하는데 네이버 웨일 브라우저에서는 바디 부분에 글자 넣은게 반영이 안되는데 그래서 이 폴더에서 index를 우클릭하여 연결프로그램 -> edge 브라우저로 연결하면 텍스트가 반영이 되는데 문제는 주소가 이렇게 폴더위치로 뜨는데 이렇게 되면 안되는거 아닌가용?ㅠㅠ 어떻게 하면 좋을까요 ㅠㅠㅠㅠㅠㅠㅠ
-
해결됨애플 웹사이트 인터랙션 클론!
if (scrollRatio <= 0.22) 로 이벤트 발생 시점을 컨트롤 하는 부분과 values 에 있는 start 와 end 값
if (scrollRatio <= 0.22) 로 이벤트 발생 시점을 컨트롤 하는 부분과 values 에 있는 start 와 end 로 처리하는 부분이 동일하지 않나요?단순히 calcValues 도 하지 않기 위해서 if (scrollRatio <= 0.22) 와 같은 조건문이 있는걸까요?
-
해결됨[2024년 출제기준] 웹디자인기능사 실기시험 완벽 가이드(HTML+CSS+JQUERY)
비주얼스튜디오 세팅부분
선생님 ㅠㅠ 비주얼 스튜디오 설치 및 사용법 영상 보면서 따라하는데, 여기서 우클릭 누르면 폴더추가가 있어야하는데 저에겐 왜 이런식으로 보일까요 ㅠㅠ 어떻게 해야 폴더 추가 할 수 있는게 되는지 알 수 있나요 ㅠㅠ 그리고 여기서 비주얼스튜디오 시작하기에서 어떤걸로 세팅해야할까요 ㅠ? 시험장에서도 이런식으로 세팅하기가 뜨나용?ㅠㅠ
-
미해결애플 웹사이트 인터랙션 클론!
이미지가 안보입니다...ㅠ(도움이 필요합니다!! 꼭 봐주세요!)
안녕하세요 선생님 강의 잘 듣고 있습니다! 다름이 아니라 밑에 수강생분 처럼 저도 이미지가 안보이고 Uncaught TypeError: Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The provided value is not of type '(CSSImageValue or HTMLCanvasElement or HTMLImageElement or HTMLVideoElement or ImageBitmap or OffscreenCanvas or SVGImageElement or VideoFrame)'. 이런 에러가 계속 뜨는데.. 어떻게 해야할지 모르겠습니다..ㅠㅠㅠ 분명 계속 이미지 경로도 확인하고 문제가 없는거같은데...대체 뭐가 문제인지 도움이 필요합니다..!! 너무 해결이 안되니까 꼭...해결하고싶습니다.. 잘되던 텍스트 애니메이션도 작동이 안됩니다. 보니까 playanimaion함수에서 sequence도 console.log에 찍으면 undefined로 찍히는데 도저히 뭐가 잘못 됐는지 모르겠습니다. 선생님이 주신 완성 코드를 대입해도 안뜨네요ㅠㅠㅠ (() => { let yOffset = 0; // window.pageYOffset대신 쓸 변수 let prevScrollHeight = 0; //현재 스크롤 위치(yOffset)보다 이전에 위치한 스크롤 섹션들의 높이값의 합 let currentScene = 0; //현재 활성화된(눈 앞에 보고있는) 씬(scroll-section) // sceneInfo는 애니메이션을 처리하는 정보를 담아놓는 변수 let enterNewScene = false;//새로운 scene이 시작되는 순간 true로 바뀜 const sceneInfo = [ { //0 type: 'sticky', heightNum:5,//브라우저 높이의 5배로 scrollHeight 세팅 scrollHeight: 0, // 애니메이션을 조작할 오브젝트를 objs에 담음 objs:{ container: document.querySelector('#scroll-section-0'), messageA: document.querySelector('#scroll-section-0 .main-message.a'), messageB: document.querySelector('#scroll-section-0 .main-message.b'), messageC: document.querySelector('#scroll-section-0 .main-message.c'), messageD: document.querySelector('#scroll-section-0 .main-message.d'), canvas: document.querySelector('#video-canvas-0'), //context객체의 메서드인 getContext를 인자 2d를 넣어 호출해 그림을 그려주는 기초를 연다. context: document.querySelector('#video-canvas-0').getContext('2d'), //비디오 이미지를 배열에 넣을 예정이다. videoImages: [] }, //오브젝트를 어느시점에 보이고 안보이게 할지를 담을 곳 css제어 //시작값 :0 끝값 :1 values: { videoImageCount: 300, //스크롤에 따른 이미지 순서의 [초기값,최종값] messageA와 다르게 섹션의 부분에서 실행될 이미지가 아닌 한스크롤에서 쭉 이어지므로 start,end구간이 필요없다 imageSequence:[0 , 299], messageA_opacity_in: [0, 1, { start: 0.1, end: 0.2 }], messageB_opacity_in: [0, 1, { start: 0.3, end: 0.4 }], //30%~ 40% messageC_opacity_in: [0, 1, { start: 0.5, end: 0.6 }], messageD_opacity_in: [0, 1, { start: 0.7, end: 0.8 }], // 글자를 20%에서 0으로 가는건데 %는 css에 나중에 작업할 예정 세번째 인수에는 타이밍이 들어간다(=어느 타이밍에 효과를 줄것인가) messageA_translateY_in: [20, 0, { start: 0.1, end: 0.2 }], messageB_translateY_in: [20, 0, { start: 0.3, end: 0.4 }], messageC_translateY_in: [20, 0, { start: 0.5, end: 0.6 }], messageD_translateY_in: [20, 0, { start: 0.7, end: 0.8 }], messageA_opacity_out: [1, 0, { start: 0.25, end: 0.3 }], messageB_opacity_out: [1, 0, { start: 0.45, end: 0.5 }], messageC_opacity_out: [1, 0, { start: 0.65, end: 0.7 }], messageD_opacity_out: [1, 0, { start: 0.85, end: 0.9 }], messageA_translateY_out: [0, -20, { start: 0.25, end: 0.3 }], messageB_translateY_out: [0, -20, { start: 0.45, end: 0.5 }], messageC_translateY_out: [0, -20, { start: 0.65, end: 0.7 }], messageD_translateY_out: [0, -20, { start: 0.85, end: 0.9 }] // messageA_opacity:[200,900] } }, { //1 type: 'normal', //heightNum은 scrollHeight를 결정할 때 innerheight의 몇배로 할지 정해주는 수 였는데 normal은 원래 본인 default높이로 설정하므로 필요가 없다 // heightNum:5, scrollHeight: 0, objs:{ container: document.querySelector('#scroll-section-1') } }, { //2 type: 'sticky', heightNum:5, scrollHeight: 0, objs:{ container: document.querySelector('#scroll-section-2'), messageA: document.querySelector('#scroll-section-2 .a'), messageB: document.querySelector('#scroll-section-2 .b'), messageC: document.querySelector('#scroll-section-2 .c'), pinB: document.querySelector('#scroll-section-2 .b .pin'), pinC: document.querySelector('#scroll-section-2 .c .pin') }, values: { messageA_translateY_in: [20, 0, { start: 0.15, end: 0.2 }], messageB_translateY_in: [30, 0, { start: 0.5, end: 0.55 }], messageC_translateY_in: [30, 0, { start: 0.72, end: 0.77 }], messageA_opacity_in: [0, 1, { start: 0.15, end: 0.2 }], messageB_opacity_in: [0, 1, { start: 0.5, end: 0.55 }], messageC_opacity_in: [0, 1, { start: 0.72, end: 0.77 }], messageA_translateY_out: [0, -20, { start: 0.3, end: 0.35 }], messageB_translateY_out: [0, -20, { start: 0.58, end: 0.63 }], messageC_translateY_out: [0, -20, { start: 0.85, end: 0.9 }], messageA_opacity_out: [1, 0, { start: 0.3, end: 0.35 }], messageB_opacity_out: [1, 0, { start: 0.58, end: 0.63 }], messageC_opacity_out: [1, 0, { start: 0.85, end: 0.9 }], pinB_scaleY: [0.5, 1, { start: 0.5, end: 0.55 }], pinC_scaleY: [0.5, 1, { start: 0.72, end: 0.77 }], pinB_opacity_in: [0, 1, { start: 0.5, end: 0.55 }], pinC_opacity_in: [0, 1, { start: 0.72, end: 0.77 }], pinB_opacity_out: [1, 0, { start: 0.58, end: 0.63 }], pinC_opacity_out: [1, 0, { start: 0.85, end: 0.9 }] } }, { //3 type: 'sticky', heightNum:5, scrollHeight: 0, objs:{ container: document.querySelector('#scroll-section-3') } } ]; function setCanvasImages() { let imgElem; for (let i = 0; i < sceneInfo[0].values.videoImageCount; i++){ //이미지 객체가 만들어짐 new Image(); 대신에 document.createElement('img')랑 같은말이다. imgElem = new Image(); imgElem.src = `./video/001/IMG_${6726 + i}.JPG`; sceneInfo[0].objs.videoImages.push(imgElem); } } setCanvasImages(); function setLayout() { //각 스크롤 섹션의 높이 세팅 for (let i = 0; i < sceneInfo.length; i++){ if (sceneInfo[i].type === 'sticky') { sceneInfo[i].scrollHeight = sceneInfo[i].heightNum * window.innerHeight; } else if (sceneInfo[i].type === 'normal') { //container본연의 높이로 가져와서 그 크기만큼으로만 normal부분을 설정한다. sceneInfo[i].scrollHeight = sceneInfo[i].objs.container.offsetHeight; } sceneInfo[i].objs.container.style.height = `${sceneInfo[i].scrollHeight}px`; } yOffset = window.pageYOffset; let totalScrollHeight = 0; for (let i = 0; i < sceneInfo.length; i++){ totalScrollHeight += sceneInfo[i].scrollHeight; if (totalScrollHeight >= yOffset) { currentScene = i; break; } } // 변수랑 문자열이 섞여있으면 ``을 써야한다. document.body.setAttribute('id', `show-scene-${currentScene}`); // console.log(sceneInfo); //초기화 할 때 실행되는 setLayout은 currentScene이 정해지지 않은 상태에서 currentScene을 구하는 것이고, // 스크롤 할 때마다 실행되는 scrollLoop는 현재 활성화된 currentScene 까지의 스크롤양(prevScrollHeight)을 기준으로 일정량의 스크롤이 지나갔을 때 currentScene을 +1 또는 -1 하는 것이랍니다. } function calcValues(values, currentYOffset) { //여기서 인자 currentYOffset은 현재 씬에서 얼마나 스크롤 됐는지 //현재 섹션에서 얼마나 스크롤 되었는지를 비율로 구한다 스클로의 비율을 0~1사이로 정한다. 비율을 구해서 그 값을 css값에 대입해준다. //yoffset변수는 전체 레이아웃에서 스크롤이 어디 위치해있는지 알려주는 변수이다. 현재 씬에서 스크롤이 얼마나됐는지는 알 수 없다. let rv; const scrollHeight = sceneInfo[currentScene].scrollHeight; //현재 씬(스크롤섹션)에서 스크롤된 범위를 비율로 구하기 ==>let scrollRation = 현재 씬에서 스크롤된 값 / 현재 씬 전체 const scrollRatio = currentYOffset / sceneInfo[currentScene].scrollHeight; if (values.length === 3) { // start~end사이에 애니메이션 실행 const partScrollStart = values[2].start * scrollHeight; const partScrollEnd = values[2].end * scrollHeight; const partScrollHeight = partScrollEnd - partScrollStart; if (currentYOffset >= partScrollStart && currentYOffset <= partScrollEnd) { rv = (currentYOffset - partScrollStart) / partScrollHeight * (values[1] - values[0]) + values[0]; } else if (currentYOffset < partScrollStart) { //스크롤섹션에서 스크롤이 start에 도달하지 못했을 때 rv = values[0]; //opacity가 0이라는 뜻 } else if (currentYOffset > partScrollEnd) { //스크롤섹션에서 스크롤이 end를 완전히 벗어났을 때 rv = values[1]; //opacity가 1이라는 뜻 } else { rv = scrollRatio * (values[1] - values[0]) + values[0]; } // rv = parseInt(scrollRatio * (values[1] - values[0]) + values[0]); return rv; } } //애니메이션 함수 function playAnimation() { // 모든 섹션에 애니메이션을 주는 것은 비효율적이므로 switch문을 이용해서 현재 보고있는 섹션의 요소에만 애니메이션을 준다 const objs = sceneInfo[currentScene].objs; const values = sceneInfo[currentScene].values; const currentYOffset = yOffset - prevScrollHeight; const scrollHeight = sceneInfo[currentScene].scrollHeight; const scrollRatio = currentYOffset / sceneInfo[currentScene].scrollHeight; switch (currentScene) { case 0: let sequence = calcValues(values.imageSequence, currentYOffset); objs.context.drawImage(objs.videoImages[sequence], 0, 0); console.log(sequence); if (scrollRatio <= 0.22) { // in objs.messageA.style.opacity = calcValues(values.messageA_opacity_in, currentYOffset); objs.messageA.style.transform = `translate3d(0, ${calcValues(values.messageA_translateY_in, currentYOffset)}%, 0)`; } else { // out objs.messageA.style.opacity = calcValues(values.messageA_opacity_out, currentYOffset); objs.messageA.style.transform = `translate3d(0, ${calcValues(values.messageA_translateY_out, currentYOffset)}%, 0)`; } if (scrollRatio <= 0.42) { // in objs.messageB.style.opacity = calcValues(values.messageB_opacity_in, currentYOffset); objs.messageB.style.transform = `translate3d(0, ${calcValues(values.messageB_translateY_in, currentYOffset)}%, 0)`; } else { // out objs.messageB.style.opacity = calcValues(values.messageB_opacity_out, currentYOffset); objs.messageB.style.transform = `translate3d(0, ${calcValues(values.messageB_translateY_out, currentYOffset)}%, 0)`; } if (scrollRatio <= 0.62) { // in objs.messageC.style.opacity = calcValues(values.messageC_opacity_in, currentYOffset); objs.messageC.style.transform = `translate3d(0, ${calcValues(values.messageC_translateY_in, currentYOffset)}%, 0)`; } else { // out objs.messageC.style.opacity = calcValues(values.messageC_opacity_out, currentYOffset); objs.messageC.style.transform = `translate3d(0, ${calcValues(values.messageC_translateY_out, currentYOffset)}%, 0)`; } if (scrollRatio <= 0.82) { // in objs.messageD.style.opacity = calcValues(values.messageD_opacity_in, currentYOffset); objs.messageD.style.transform = `translate3d(0, ${calcValues(values.messageD_translateY_in, currentYOffset)}%, 0)`; } else { // out objs.messageD.style.opacity = calcValues(values.messageD_opacity_out, currentYOffset); objs.messageD.style.transform = `translate3d(0, ${calcValues(values.messageD_translateY_out, currentYOffset)}%, 0)`; } break; case 2: // console.log('2 play'); if (scrollRatio <= 0.25) { // in objs.messageA.style.opacity = calcValues(values.messageA_opacity_in, currentYOffset); objs.messageA.style.transform = `translate3d(0, ${calcValues(values.messageA_translateY_in, currentYOffset)}%, 0)`; } else { // out objs.messageA.style.opacity = calcValues(values.messageA_opacity_out, currentYOffset); objs.messageA.style.transform = `translate3d(0, ${calcValues(values.messageA_translateY_out, currentYOffset)}%, 0)`; } if (scrollRatio <= 0.57) { // in objs.messageB.style.transform = `translate3d(0, ${calcValues(values.messageB_translateY_in, currentYOffset)}%, 0)`; objs.messageB.style.opacity = calcValues(values.messageB_opacity_in, currentYOffset); objs.pinB.style.transform = `scaleY(${calcValues(values.pinB_scaleY, currentYOffset)})`; } else { // out objs.messageB.style.transform = `translate3d(0, ${calcValues(values.messageB_translateY_out, currentYOffset)}%, 0)`; objs.messageB.style.opacity = calcValues(values.messageB_opacity_out, currentYOffset); objs.pinB.style.transform = `scaleY(${calcValues(values.pinB_scaleY, currentYOffset)})`; } if (scrollRatio <= 0.83) { // in objs.messageC.style.transform = `translate3d(0, ${calcValues(values.messageC_translateY_in, currentYOffset)}%, 0)`; objs.messageC.style.opacity = calcValues(values.messageC_opacity_in, currentYOffset); objs.pinC.style.transform = `scaleY(${calcValues(values.pinC_scaleY, currentYOffset)})`; } else { // out objs.messageC.style.transform = `translate3d(0, ${calcValues(values.messageC_translateY_out, currentYOffset)}%, 0)`; objs.messageC.style.opacity = calcValues(values.messageC_opacity_out, currentYOffset); objs.pinC.style.transform = `scaleY(${calcValues(values.pinC_scaleY, currentYOffset)})`; } break; case 3: // console.log('3 play'); break; } } // keyframe이란: 애니메이션이 진행중에 변화가 있는 지점을 keyframe이라고 한다. function scrollLoop() { enterNewScene = false; prevScrollHeight = 0;//누적을 막기 위해서 // 현재 보고잇는 section이 몇번째 section인지 판별하려고함 for (let i = 0; i < currentScene; i++){ prevScrollHeight = prevScrollHeight + sceneInfo[i].scrollHeight; } // console.log(prevScrollHeight); if (yOffset > prevScrollHeight + sceneInfo[currentScene].scrollHeight) { enterNewScene = true; currentScene++; document.body.setAttribute('id', `show-scene-${currentScene}`); // setLayout함수에서 이미 id값이 정해져있지만 스크롤에 변함에 따라 체크하는 방식으로 쓴다 } if (yOffset < prevScrollHeight) { if (currentScene === 0) return;//브라우저 바운스 효과로 인해 마이너스가 되는 것을 방지(모바일) enterNewScene = true; currentScene--; document.body.setAttribute('id', `show-scene-${currentScene}`); } // console.log(currentScene); // enterNewScene이 참이라는것은 씬이 바뀌는순간인 것이고 참일때 return, 즉 playAnimation함수를 한타임 실행하지 않는다는것으로 음수값이 무시되고 다시 playAnimation함수가 실행된다. if (enterNewScene) return; playAnimation(); } window.addEventListener('scroll', () => { yOffset = window.pageYOffset; scrollLoop(); }); window.addEventListener('load', setLayout); // window의 모든 문서(html, 이미지, 동영상..)가 완료되고 나서 // 레이아웃 잡는 setLayout함수를 실행한다 // window.addEventListener('DOMContentLoaded', setLayout); //반대로 window의 html구조들만 완료되면 반대로 이미지는 로드되지않아도 setlayout함수를 실행하는것 //인데 load보다는 빨리 setlayout을 실행한다. window.addEventListener('resize', setLayout); // 윈도우가 리사이즈 되었을 때 setLayout을 호출한다 })(); <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>AirMug Pro</title> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;900&display=swap" rel="stylesheet"> <link rel="stylesheet" href="css/default.css"> <link rel="stylesheet" href="css/main.css"> <script defer src="js/main.js"></script> </head> <body> <div class="container"> <nav class="global-nav"> <div class="global-nav-links"> <a href="#" class="global-nav-item">Rooms</a> <a href="#" class="global-nav-item">Ideas</a> <a href="#" class="global-nav-item">Stores</a> <a href="#" class="global-nav-item">Contact</a> </div> </nav> <nav class="local-nav"> <div class="local-nav-links"> <a href="#" class="product-name">AirMug Pro</a> <a href="#">개요</a> <a href="#">제품사양</a> <a href="#">구입하기</a> </div> </nav> <section class="scroll-section" id="scroll-section-0"> <h1>AirMug Pro</h1> <!-- p태그의 값들을 sticky, 즉 스티커처럼 고정해놓고 스트롤의 값에 따라 보여지도록 p태그를 감싸고 있는 div에 class를 준다. --> <div class="sticky-elem sticky-elem-canvas"> <canvas id ="video-canvas-0" width="1920px" height="1080px"></canvas> </div> <div class="sticky-elem main-message a"> <p>온전히 빠져들게 하는<br>최고급 세라믹</p></div> <div class="sticky-elem main-message b"> <p>주변 맛을 느끼게 해주는<br>주변 맛 허용 모드</p> </div> <div class="sticky-elem main-message c"> <p>온종일 편안한<br>맞춤형 손잡이</p> </div> <div class="sticky-elem main-message d"> <p>새롭게 입가를<br>찾아온 매혹</p> </div> </section> <section class="scroll-section" id="scroll-section-1"> <p class="description"> <strong>보통 스크롤 영역</strong> Lorem ipsum dolor sit amet consectetur adipisicing elit. Quas minus praesentium incidunt tempora repellendus? Laudantium sed excepturi quo nemo quisquam totam, cumque nam sit incidunt, corrupti libero temporibus obcaecati enim facilis dolore itaque minima dolorem sequi explicabo. Modi alias in ullam labore, minima voluptates ipsa ex laborum deleniti tempora incidunt architecto iure aut, molestias ab! Fugiat aliquam veritatis ad voluptatibus, quaerat officia doloribus aliquid nulla repellat quidem rem magni perferendis labore similique reprehenderit aut. Cum quis omnis ex iusto natus iste nisi magnam, nesciunt fugit aspernatur ullam? Temporibus, dolores ipsa. Odit odio aliquam sunt error corporis ab facere, illum sequi! Explicabo nulla, ipsum necessitatibus laboriosam eligendi aspernatur quam nostrum. Debitis est ad, vel in corrupti voluptas laboriosam quo, reiciendis quos quas culpa, atque voluptate tempora tempore quam accusamus ipsa asperiores voluptatibus adipisci non vero aspernatur eum nesciunt quis. A cum, molestiae eaque repellat similique esse eligendi reprehenderit amet omnis minus quae voluptatum delectus dolores repudiandae? Distinctio aut aspernatur, iure nemo, eum soluta error necessitatibus minus voluptatum laborum rem dignissimos quae. Error ut recusandae ullam magnam quas molestiae repellat culpa. Ab nihil sint voluptatum in? Enim suscipit debitis earum culpa sint maxime hic, repudiandae nulla ad error voluptatem fugiat blanditiis odit! </p> </section> <section class="scroll-section" id="scroll-section-2"> <div class="sticky-elem main-message a"> <p> <small>편안한 촉감</small> 입과 하나 되다 </p> </div> <div class="sticky-elem desc-message b"> <p> 편안한 목넘김을 완성하는 디테일한 여러 구성 요소들, 우리는 이를 하나하나 새롭게 살피고 재구성하는 과정을 거쳐 새로운 수준의 머그, AirMug Pro를 만들었습니다. 입에 뭔가 댔다는 감각은 어느새 사라지고 오롯이 당신과 음료만 남게 되죠. </p> <div class="pin"></div> </div> <div class="sticky-elem desc-message c"> <p> 디자인 앤 퀄리티 오브 스웨덴,<br>메이드 인 차이나 </p> <div class="pin"></div> </div> </section> <section class="scroll-section" id="scroll-section-3"> <p class="mid-message"> <strong>Retina 머그</strong><br> 아이디어를 광활하게 펼칠<br> 아름답고 부드러운 음료 공간. </p> <p class="canvas-caption"> Lorem ipsum dolor, sit amet consectetur adipisicing elit. Eveniet at fuga quae perspiciatis veniam impedit et, ratione est optio porro. Incidunt aperiam nemo voluptas odit quisquam harum in mollitia. Incidunt minima iusto in corporis, dolores velit. Autem, sit dolorum inventore a rerum distinctio vero illo magni possimus temporibus dolores neque adipisci, repudiandae repellat. Ducimus accusamus similique quas earum laborum. Autem tempora repellendus asperiores illum ex! Velit ea corporis odit? Ea, incidunt delectus. Sapiente rerum neque error deleniti quis, et, quibusdam, est autem voluptate rem voluptas. Ratione soluta similique harum nihil vel. Quas inventore perferendis iusto explicabo animi eos ratione obcaecati. </p> </section> <footer class="footer"> 2020, 1분코딩 </footer> </div> </body> </html>
-
미해결애플 웹사이트 인터랙션 클론!
공간을 넘어가요!
공간을 넘어가서 lorem 값이 나오는데 왜 이러는지 알 수 있을까요...?! padding 값이 문제일까요 ??? 문제는 없는 것 같은데 원래 넘어갈 수 밖에 없는 건지 궁금합니다.
-
미해결피그마(Figma)를 활용한 UI디자인 입문부터 실전까지 A to Z
그리드 숨기기
그리드 숨기기 단축키를 누르면 (컨트롤+g) 누구면 그룹화되고 그리드는 안없어지네요ㅜㅜ
-
미해결[입문] 예민한 UX 디자인
책 질문드립니다.
ux관련 수업에서 연세대학교 교수님의 human computer interface 책 추천해주셨는데, 교수님 성함이 잘 안들려서 궁금하고, 책도 인터넷 검색해도 나오지 않아서.. 유튜브에 무료 강의 보려면 어떤 검색어로 검색해야하는지, 또 책은 어디서 구매할 수 있는지 궁금합니다.
-
미해결피그마(Figma)를 활용한 UI디자인 입문부터 실전까지 A to Z
피그마에서 큰 이미지 불러올 시 사이즈 자동 리사이징 현상
로고나 아이콘, 썸네일 등에 사용 할 작은 이미지들은 문제가 없는데 whith 1000px 이상 넘어가는 이미지 불러올 시 원본 사이즈보다 작게 리사이징 되는데, 왜 그런건지 이유를 알 수 있을까요? 예를 들어 웹사이트를 통으로 캡쳐 후(1920px X @) 피그마로 불러오면 원본 사이즈는 1920px보다 작게 출력이 됩니다. 늘리면 당연히 깨지겠지요.. 포토샵에서는 원본 그대로 불러오기 됩니다. 답변 부탁 드립니다!
-
미해결반응형 웹사이트 포트폴리오(Architecture Agency)
두가지 오류사항 질문드립니다. (live server Full Reload 오류 / position: relative 관련)
1. 첫번째 질문입니다. (live server Full Reload 스크롤 위로 올라가는 현상) 라이브서버 세팅을 할때 Full Reload 체크를 하고 재시동을 했을 때 문제없이 작동을 했습니다. 그런데 이번 강의[섹션 4. 섹션 상세 퍼블리싱(Awards Section) - PC 레이아웃 섹션 상세 퍼블리싱(Awards Section) - #01 ] 부터 Awards section을 수정하다가 라이브로 확인하려고 했으나 계속해서 상단 부분으로 스크롤이 올라가는 바람에 수정하며 바로확인하는 과정에 불편함을 겪었습니다. 이 부분을 해결하고자, 각 섹션별 display: none; 도 주었지만- 역시나 작동하지 않아서 구글링을 한 끝에 https://github.com/tapio/live-server/issues/273 저와 같은 이슈를 갖고 있는 분들을 발견했음에도 불구- 해결을 하지 못한 상태입니다. 혹시나 싶어서 라이브서버를 unstall 하고 재설치를 한 후에, 다시 세팅을 하고 재시동을 하는 과정을 몇번이고 반복했으나 여전히 같은 이슈가 발생했습니다. 앞서 말씀드린.. VS code 의 Live Server Full Reload 오류현상을 해결하는 방법을 혹시 아실까 싶어 선생님께 질문을 드리고 싶었습니다(: 2. 두번째 질문입니다. ( position: relative 관련) .victory-jump img 에 position: absolute;를 적용할 때 선생님께서 말씀하시길, 그 위의 부모인 .victor-jump 에 position: relative;를 적용하지 않아도 되는 이유가 .awards-inner 안에 있는 div에 이미 position: relative;를 주었기 때문이라고 하셨습니다. 제가 잘 모르는 것일 수도 있겠지만- 지금까지 공부한 바로 .awards-inner > div 에서 '>' 표시는 그 부모 바로 밑에 있는 첫번째 자식 에게 적용되는 것으로 알고 있습니다. 때문에 .awards-inner의 두번째 자식인 .vicotry-jump 가 아닌 첫번째 자식 .about-awards 에만 적용되는 것으로 알았습니다. 제가 잘 못 알고 있는건지 궁금합니다(: 더불어, 두번째 자식인 .victory-jump 에 position: relative; 를 적용하지 않아도 .victory-jump 의 자식인 .victory-jump img 에 position: absolute; 만 적용해도 화면에 문제없이 잘 나타나는 이유도 궁금합니다.:) 감사합니다.
-
미해결애플 웹사이트 인터랙션 클론!
main-add.js 코드 오류 있습니다. 이거 보고 수정하세요
main-add.js 내용 적용했는데 section-2 부분의 messageC부분이 section3까지 넘어가서 뭔가 이상하다고 생각했습니다. 실제로, 강의 영상에 나온 것이랑 값이 차이나서 수정한 부분 올립니다. sceneInfo의 section2부분의 value만 변경해주시면 됩니다. values: { messageA_translateY_in: [20, 0, { start: 0.15, end: 0.2 }], messageB_translateY_in: [30, 0, { start: 0.5, end: 0.55 }], messageC_translateY_in: [30, 0, { start: 0.72, end: 0.77 }], messageA_opacity_in: [0, 1, { start: 0.15, end: 0.2 }], messageB_opacity_in: [0, 1, { start: 0.5, end: 0.55 }], messageC_opacity_in: [0, 1, { start: 0.72, end: 0.77 }], messageA_translateY_out: [0, -20, { start: 0.3, end: 0.35 }], messageB_translateY_out: [0, -20, { start: 0.58, end: 0.63 }], messageC_translateY_out: [0, -20, { start: 0.85, end: 0.9 }], messageA_opacity_out: [1, 0, { start: 0.3, end: 0.35 }], messageB_opacity_out: [1, 0, { start: 0.58, end: 0.63 }], messageC_opacity_out: [1, 0, { start: 0.85, end: 0.9 }], pinB_scaleY: [0.5, 1, { start: 0.5, end: 0.55 }], pinC_scaleY: [0.5, 1, { start: 0.72, end: 0.77 }], pinB_opacity_in: [0, 1, { start: 0.5, end: 0.55 }], pinC_opacity_in: [0, 1, { start: 0.72, end: 0.77 }], pinB_opacity_out: [1, 0, { start: 0.58, end: 0.63 }], pinC_opacity_out: [1, 0, { start: 0.85, end: 0.9 }], },